﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TiposComuns.Objectos;
using System.Collections;

namespace Pesquisa
{
    /// <summary>
    /// This class keeps the k nodes closest to 
    /// a given key
    /// </summary>
    [Serializable]
    public class KClosest : IEnumerable<Person>
    {
        #region Static
        /// <summary>
        /// Obtains the distance between two hash values, based on the xor metric
        /// </summary>
        /// <param name="k1">1st hash value</param>
        /// <param name="k2">2nd hash value</param>
        /// <returns>Distance, as a biginteger</returns>
        public static BigInteger CalculateDistance(byte []k1,byte []k2)
        {
            byte []res = new byte[k1.Length];

            for (int i = 0; i < k1.Length; i++)
            {
                res[i] = (byte)(k1[i] ^ k2[i]);
            }

            return new BigInteger(res);
        }
        #endregion

        #region Attributes

        /// <summary>
        /// Key
        /// </summary>
        public byte[] Key
        {
            get;
            set;
        }

        private SortedList<BigInteger,Person> nodes;         //K closest nodes, ordered by the distance to the user
        private List<Person> recent;                         //Holds recent added nodes whith whom there wasn't any comunication

        #endregion

        #region Constructors
        public KClosest(byte[] key, SortedList<BigInteger,Person> nodes)
        {
            this.Key = key;
            this.nodes = nodes;
            this.recent = new List<Person>();
            this.recent.AddRange(nodes.Values);
        }

        #endregion

        #region Methods

        public static KClosest ToKClosest(byte []hash, KBucket buc)
        {
            SortedList<BigInteger, Person> list = new SortedList<BigInteger, Person>();
            foreach (Person per in buc)
            {
                list.Add(KClosest.CalculateDistance(per.Hash, hash), per);
            }
            return new KClosest(hash,list);
        }
        
        /// <summary>
        /// Tries to add all the elements to this structure, preserving only the k-closest to the user's key
        /// </summary>
        /// <param name="news">K-nodes to insert</param>
        /// <returns>True, if any of the new node is inserted</returns>
        public bool AddAll(KClosest news)
        {
            bool flag = false;
            foreach (Person person in news.nodes.Values)
            {
                BigInteger dist = KClosest.CalculateDistance(person.Hash, this.Key);

                if (nodes.Count == KBucket.K && nodes.Last().Key > dist)
                {
                    nodes.RemoveAt(nodes.Count - 1);
                    recent.Remove(nodes.Last().Value);

                    nodes.Add(dist, person);
                    recent.Add(person);

                    flag = true;
                }
                else if (nodes.Count < KBucket.K)
                {
                    nodes.Add(dist, person);
                    recent.Add(person);
                }
            }

            return flag;
        }

        /// <summary>
        /// Return the num most recent nodes with whom there wasn't a communication
        /// </summary>
        /// <param name="num">Number of returned nodes</param>
        /// <returns>Nodes</returns>
        public List<Person> GetRecent(int num)
        {
            List<Person> list = new List<Person>();
            for (int i = Math.Min(recent.Count - 1, num - 1); i >= 0; i--)
            {
                list.Add(recent[i]);
                recent.RemoveAt(i);
            }

            return list;
        }

        #endregion

        #region IEnumerable<Person> Members

        IEnumerator<Person> IEnumerable<Person>.GetEnumerator()
        {
            return this.nodes.Values.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.nodes.GetEnumerator();
        }

        #endregion
    }
}
